package com.jay.media.music.ui.manager

import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.media.MediaMetadata
import android.media.browse.MediaBrowser
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.jay.media.kit.LogHelper
import com.jay.media.music.core.MusicService
import com.jay.media.music.core.constant.Actions
import com.jay.media.music.core.constant.Actions.ACTION_OPERATE_FAVOR
import com.jay.media.music.core.constant.Actions.ACTION_OPERATE_PLAY_LIST
import com.jay.media.music.core.constant.Keys
import com.jay.media.music.core.provider.METADATA_FAVOR
import java.lang.ref.WeakReference
import java.util.concurrent.CopyOnWriteArrayList

/**
 * @author xuzhou4
 * @date 2022/3/8
 */
class MusicClientManager {
    companion object {
        private val TAG = LogHelper.makeLogTag(MusicClientManager::class.java)
        private const val CONNECT_INTERVAL = 1_000L
        private val CLIENT by lazy {
            MusicClientManager()
        }

        fun getClient(): MusicClientManager {
            return CLIENT
        }
    }

    private lateinit var mContext: Context
    private var mHandler: Handler = Handler(Looper.getMainLooper())
    private var mMediaBrowser: MediaBrowser? = null
    private lateinit var mMediaController: MediaController
    private var mMediaListeners: MutableList<WeakReference<MusicListener>> = CopyOnWriteArrayList()

    var mMetadata: MediaMetadata? = null
    var mPlaybackState: PlaybackState? = null
    var mMediaId: String? = null
    var mSessionId: Int = 0
    var mRoot: String? = null

    private var mMediaListenerProxy = object : MusicListener {
        override fun onChildrenLoaded(
            parentId: String,
            children: MutableList<MediaBrowser.MediaItem>
        ) {
            mMediaListeners.forEach {
                it.get()?.onChildrenLoaded(parentId, children)
            }
        }

        override fun onMetadataChanged(metadata: MediaMetadata?) {
            mMediaListeners.forEach {
                it.get()?.onMetadataChanged(metadata)
            }
        }

        override fun onPlaybackStateChanged(state: PlaybackState?) {
            mMediaListeners.forEach {
                it.get()?.onPlaybackStateChanged(state)
            }
        }

        override fun onSessionEvent(event: String, extras: Bundle?) {
            mMediaListeners.forEach {
                it.get()?.onSessionEvent(event, extras)
            }
        }
    }

    private var mMediaBrowserConnectionCallback = object : MediaBrowser.ConnectionCallback() {
        override fun onConnected() {
            super.onConnected()
            mSessionId = mMediaBrowser?.extras?.getInt(Keys.KEY_AUDIO_SESSION_ID) ?: 0
            LogHelper.d(TAG, "onConnected : audioId -> $mSessionId")

            mRoot = mMediaBrowser?.root ?: ""
            connectSession(mMediaBrowser?.sessionToken)
        }

        override fun onConnectionSuspended() {
            super.onConnectionSuspended()
            LogHelper.e(TAG, "onConnectionSuspended")

            mHandler.postDelayed({ connect() }, CONNECT_INTERVAL)
        }

        override fun onConnectionFailed() {
            super.onConnectionFailed()
            LogHelper.e(TAG, "onConnectionFailed")

            mHandler.postDelayed({ connect() }, CONNECT_INTERVAL)
        }
    }

    private var mMediaControllerCallback = object : MediaController.Callback() {

        override fun onPlaybackStateChanged(state: PlaybackState?) {
            super.onPlaybackStateChanged(state)
            LogHelper.d(TAG, "onPlaybackStateChanged() called with: state = $state")
            mPlaybackState = state
            mMediaListenerProxy.onPlaybackStateChanged(state)
        }

        override fun onMetadataChanged(metadata: MediaMetadata?) {
            super.onMetadataChanged(metadata)
            LogHelper.d(TAG, "onMetadataChanged() called with: metadata = $metadata")
            mMetadata = metadata

            mMediaListenerProxy.onMetadataChanged(metadata)
        }

        override fun onSessionEvent(event: String, extras: Bundle?) {
            super.onSessionEvent(event, extras)
            LogHelper.d(TAG, "onSessionEvent() called with: event = $event, extras = $extras")
            if (event == Actions.ACTION_AUDIO_SESSION_ID) {
                mSessionId = extras?.getInt(Keys.KEY_AUDIO_SESSION_ID) ?: 0
                LogHelper.d(TAG, "onSessionEvent() called with: sessionID = $mSessionId")
            }

            mMediaListenerProxy.onSessionEvent(event, extras)
        }
    }

    private var mSubscriptionCallBack: MediaBrowser.SubscriptionCallback = object :
        MediaBrowser.SubscriptionCallback() {

        override fun onChildrenLoaded(
            parentId: String,
            children: MutableList<MediaBrowser.MediaItem>
        ) {
            super.onChildrenLoaded(parentId, children)
            Log.d(TAG, "onChildrenLoaded: parentId = $parentId, children = $children")

            mMediaListenerProxy.onChildrenLoaded(parentId, children)
        }

        override fun onError(parentId: String) {
            super.onError(parentId)
            LogHelper.d(TAG, "onError: parentId = $parentId")
        }
    }

    fun init(context: Context) {
        this.mContext = context

        connect()
    }

    private fun connect() {
        LogHelper.d(TAG, "connect media server")
        if (mMediaBrowser == null) {
            mMediaBrowser = MediaBrowser(
                mContext, ComponentName(mContext, MusicService::class.java),
                mMediaBrowserConnectionCallback,
                null
            )
        }
        mMediaBrowser?.connect()
    }

    private fun connectSession(sessionToken: MediaSession.Token?) {
        LogHelper.d(TAG, "connectSession $sessionToken")

        sessionToken?.also {
            mMediaController = MediaController(mContext, sessionToken)
            mMediaController.registerCallback(mMediaControllerCallback)
        }
    }

    fun subscribe(mediaId: String? = mRoot) {
        mMediaId?.let {
            mMediaBrowser?.unsubscribe(it)
        }

        mediaId?.let {
            this.mMediaId = mediaId
            mMediaBrowser?.subscribe(it, mSubscriptionCallBack)
        }
    }

    fun unsubscribe() {
        mMediaId?.let {
            mMediaBrowser?.subscribe(it, mSubscriptionCallBack)
        }
        this.mMediaId = null
    }

    fun playFromMediaId(mediaId: String?, extras: Bundle? = null) {
        mMediaController.transportControls.playFromMediaId(mediaId, extras ?: Bundle())
    }

    fun playOrPause() {
        LogHelper.d(TAG, "playOrPause ${mPlaybackState?.state}")
        if (mPlaybackState?.state == PlaybackState.STATE_PLAYING) {
            mMediaController.transportControls.pause()
        } else {
            mMediaController.transportControls.play()
        }
    }

    fun prev() {
        LogHelper.d(TAG, "prev")
        mMediaController.transportControls.skipToPrevious()
    }

    fun next() {
        LogHelper.d(TAG, "next")
        mMediaController.transportControls.skipToNext()
    }

    fun seekTo(progress: Int) {
        LogHelper.d(TAG, "seekTo $progress")
        mMediaController.transportControls.seekTo(progress.toLong())
    }

    fun sendCustomAction(action: String, args: Bundle? = null) {
        mMediaController.transportControls.sendCustomAction(action, args)
    }

    @SuppressLint("WrongConstant")
    fun favor() {
        mMetadata?.let {
            val musicId = it.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
            val favor = it.getString(METADATA_FAVOR)
            musicId?.let {
                favor(musicId, favor != "true")
            }
        }
    }

    fun favor(favor: Boolean) {
        mMetadata?.let {
            val musicId = it.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
            musicId?.let {
                favor(musicId, favor)
            }
        }
    }

    private fun favor(musicId: String, favor: Boolean) {
        val args = Bundle()
        args.putString(Keys.KEY_MUSIC_ID, musicId)
        args.putBoolean(Keys.KEY_OPERATE_FAVOR_TYPE, favor)
        mMediaController.transportControls.sendCustomAction(ACTION_OPERATE_FAVOR, args)
    }

    fun operatePlayList(operate: Boolean) {
        mMetadata?.let {
            operatePlayList(it, operate)
        }
    }

    fun operatePlayList(metadata: MediaMetadata, operate: Boolean) {
        val musicId = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
        operatePlayList(musicId, operate)
    }

    fun operatePlayList(musicId: String?, operate: Boolean) {
        musicId?.let {
            val args = Bundle()
            args.putString(Keys.KEY_MUSIC_ID, musicId)
            args.putBoolean(Keys.KEY_OPERATE_PLAY_LIST_TYPE, operate)
            mMediaController.transportControls.sendCustomAction(ACTION_OPERATE_PLAY_LIST, args)
        }
    }

    fun registerMediaListener(listener: MusicListener) {
        unregisterMediaListener(listener)
        mMediaListeners.add(WeakReference(listener))
    }

    fun unregisterMediaListener(listener: MusicListener) {
        for (mediaListener in mMediaListeners) {
            if (mediaListener.get()?.equals(listener) == true) {
                mMediaListeners.remove(mediaListener)
            }
        }
    }
}